package cn.ad.server.persistence;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import cn.ad.server.common.exception.AdServerException;
import cn.ad.server.common.utils.Constants;
import cn.ad.server.common.utils.monitor.MonitorHandler;
import cn.ad.server.domain.ad.*;
import cn.ad.server.domain.ad.vo.CampaignVO;
import cn.ad.server.domain.ad.vo.StrategyGroupVO;
import cn.ad.server.domain.ad.vo.StrategyVO;
import cn.ad.server.domain.enumration.ResponseStatusCode;
import cn.ad.server.persistence.dao.DayPartingDao;
import cn.ad.server.persistence.dao.StrategyDao;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Repository;

import cn.ad.server.common.exception.AdServerException;
import cn.ad.server.common.utils.Constants;
import cn.ad.server.common.utils.monitor.MonitorHandler;
import cn.ad.server.persistence.dao.CampaignDao;
import cn.ad.server.domain.ad.AmountStore;
import cn.ad.server.domain.ad.Campaign;
import cn.ad.server.domain.ad.DayTargert;
import cn.ad.server.domain.ad.GuidValueObject;
import cn.ad.server.domain.ad.Strategy;
import cn.ad.server.domain.ad.StrategyGroup;
import cn.ad.server.domain.ad.vo.CampaignVO;
import cn.ad.server.domain.ad.vo.StrategyGroupVO;
import cn.ad.server.domain.ad.vo.StrategyVO;
import cn.ad.server.domain.enumration.ResponseStatusCode;
import org.springframework.util.CollectionUtils;

@Repository
public class StrategyRepositoryImpl implements StrategyRepository {

	private static Logger logger = LoggerFactory.getLogger(StrategyRepositoryImpl.class);

	public final static int STATUS_VALID = 1;
	/**
	 * 24小时时间位
	 */
	private static int[] masks = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536,
			131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608 };

	@Autowired
	private StrategyDao strategyDao;

	@Autowired
	private CampaignDao campaignDao;

	@Autowired
	private ConfigRepository configRepository;

	@Resource(name = "cacheRedisTemplate")
	private ValueOperations<String, String> stringValueOperations;

	@Resource(name = "cacheRedisTemplate")
	private ValueOperations<String, Strategy> strategyOperations;

	@Resource(name = "countRedisTemplate")
	private ValueOperations<String, Double> countOperations;

	@Resource(name = "cacheRedisTemplate")
	private RedisTemplate<String, String> cacheRedisTemplate;

	@Resource(name = "countRedisTemplate")
	private RedisTemplate<String, String> countRedisTemplate;

	@Resource(name = "guidRedisTemplate")
	private RedisTemplate<String, GuidValueObject> guidRedisTemplate;

	@Resource(name = "guidRedisTemplateV2")
	private RedisTemplate<String, GuidValueObject> guidRedisTemplateV2;

	@Autowired
	private RedisScript<Boolean> balanceScript;

	// 根据key和value放入redis计数，同时设定key的过期时间
	double increment(String key, Double value, long expireTime) {
		Number i = countOperations.increment(key, value);
		if (i == null || i.doubleValue() == value) {
			countRedisTemplate.expire(key, expireTime, TimeUnit.HOURS);
		}
		return i.doubleValue();
	}

	void incrTransaction(final List<List<String>> list) {
		if (CollectionUtils.isEmpty(list))
			return;
		countRedisTemplate.execute(new SessionCallback<List<Object>>() {
			@SuppressWarnings({ "rawtypes", "unchecked" })
			public List<Object> execute(RedisOperations operations) throws DataAccessException {
				operations.multi();

				for (List<String> p : list) {
					String key = p.get(0);
					double value = Double.valueOf(p.get(1));
					long expireTime = Long.valueOf(p.get(2));

					Number i = operations.opsForValue().increment(key, value);
					if (i == null || i.doubleValue() == value) {
						operations.expire(key, expireTime, TimeUnit.HOURS);
					}
				}
				return operations.exec();
			}
		});
	}

	void incrementCPCBudget(List<List<String>> ps, StrategyVO s, CampaignVO c, StrategyGroupVO sg) {
		double singleBudget = s.getUnitPrice();

		ps.add(toParamList(Strategy.maxBudgetKey(s.getId()), singleBudget,
				Strategy.maxBudgetExpireHour(s.getEndTimeSecond())));
		ps.add(toParamList(Strategy.dayBudgetKey(s.getId()), singleBudget, 25));

		ps.add(toParamList(Campaign.maxBudgetKey(c.getId()), singleBudget,
				Campaign.maxBudgetExpireHour(c.getEndTimeSecond())));
		ps.add(toParamList(Campaign.dayBudgetKey(c.getId()), singleBudget, 25));

		if (sg != null) {
			ps.add(toParamList(StrategyGroup.dayBudgetKey(sg.getId()), singleBudget, 25));
		}

	}

	void incrementCPCBudget(List<List<String>> ps, Strategy s) {
		double singleBudget = s.getUnitPrice().doubleValue();

		ps.add(toParamList(s.maxBudgetKey(), singleBudget, s.maxBudgetExpireHour()));
		ps.add(toParamList(s.dayBudgetKey(), singleBudget, 25));

		ps.add(toParamList(s.getCampaign().maxBudgetKey(), singleBudget, s.getCampaign().maxBudgetExpireHour()));
		ps.add(toParamList(s.getCampaign().dayBudgetKey(), singleBudget, 25));

		if (s.getStrategyGroup() != null) {
			ps.add(toParamList(s.getStrategyGroup().dayBudgetKey(), singleBudget, 25));
		}

	}

	@Override
	public double singleCPMBudget(BigDecimal unitPrice) {
		return unitPrice.divide(BigDecimal.valueOf(1000), 4, RoundingMode.HALF_DOWN).doubleValue();
	}

	@Override
	public double singleCPMBudget(double unitPrice) {
		return new BigDecimal(unitPrice).divide(BigDecimal.valueOf(1000), 4, RoundingMode.HALF_DOWN).doubleValue();
	}

	@Deprecated
	void incrementCPMBudget(List<List<String>> ps, Strategy s) {
		double singleBudget = singleCPMBudget(s.getUnitPrice());
		ps.add(toParamList(s.maxBudgetKey(), singleBudget, s.maxBudgetExpireHour()));
		ps.add(toParamList(s.dayBudgetKey(), singleBudget, 25));

		ps.add(toParamList(s.getCampaign().maxBudgetKey(), singleBudget, s.getCampaign().maxBudgetExpireHour()));
		ps.add(toParamList(s.getCampaign().dayBudgetKey(), singleBudget, 25));

		if (s.getStrategyGroup() != null) {
			ps.add(toParamList(s.getStrategyGroup().dayBudgetKey(), singleBudget, 25));
		}
	}

	void incrementCPMBudget(List<List<String>> ps, StrategyVO s, CampaignVO c, StrategyGroupVO sg) {
		double singleBudget = singleCPMBudget(s.getUnitPrice());
		ps.add(toParamList(Strategy.maxBudgetKey(s.getId()), singleBudget,
				Strategy.maxBudgetExpireHour(s.getEndTimeSecond())));
		ps.add(toParamList(Strategy.dayBudgetKey(s.getId()), singleBudget, 25));

		ps.add(toParamList(Campaign.maxBudgetKey(c.getId()), singleBudget,
				Campaign.maxBudgetExpireHour(c.getEndTimeSecond())));
		ps.add(toParamList(Campaign.dayBudgetKey(c.getId()), singleBudget, 25));

		if (sg != null) {
			ps.add(toParamList(StrategyGroup.dayBudgetKey(sg.getId()), singleBudget, 25));
		}
	}

	List<String> toParamList(String key, double value, long time) {
		List<String> sm = new ArrayList<String>();
		sm.add(key);
		sm.add(String.valueOf(value));
		sm.add(String.valueOf(time));
		return sm;
	}

	@Override
	public void countClick(Strategy s) {

		if (s == null) {
			return;
		} else if (s.getCampaign() == null) {
			return;
		}
		// else if (s.getStrategyGroup() == null) {
		// MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
		// configRepository.fetchDebugMode(),
		// Strategy.class.getName(), String.valueOf(s.getId()), "strategyGroup
		// is null . @CLICK");
		// return;
		// } else if (s.getStrategyGroup().getStatus() != 1) {
		// MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
		// configRepository.fetchDebugMode(),
		// Strategy.class.getName(), String.valueOf(s.getId()),
		// "strategyGroup's status is not available. @CLICK");
		// return;
		// }

		List<List<String>> ps = new ArrayList<List<String>>();

		// 订单计数

		ps.add(toParamList(s.getCampaign().maxClicksKey(), 1.0, s.getCampaign().maxClicksExpireHour()));
		ps.add(toParamList(s.getCampaign().dayClicksKey(), 1.0, 25));

		ps.add(toParamList(s.maxClicksKey(), 1.0, s.maxClicksExpireHour()));
		ps.add(toParamList(s.dayClicksKey(), 1.0, 25));

		if (s.getFeeType() == 0)
			this.incrementCPCBudget(ps, s);

		// long ss = System.currentTimeMillis();
		this.incrTransaction(ps);
		// logger.info("----- {}", System.currentTimeMillis() - ss);

		if (s.getFeeType() == 0) {
			if (s.getStrategyGroup() != null && s.getStrategyGroup().getStatus() == 1) {
				if (s.getStrategyGroup().getSpeedMode() == 0) {
					decreaseStrategyGroupBalance(s, s.getUnitPrice().doubleValue());
				}
			} else {
				if (s.getSpeedMode() == 0 && (s.getSpeedBase() == 0 || s.getSpeedBase() == 10 || s.getSpeedBase() == 2
						|| s.getSpeedBase() == 12))
					decreaseBalance(s);
			}
		}
		// logger.info("----- {}", System.currentTimeMillis() - ss);
	}

	@Override
	public void countImpressions(Strategy s) {
		if (s == null) {
			return;
		} else if (s.getCampaign() == null) {
			return;
		}
		// else if (s.getStrategyGroup() == null) {
		// MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
		// configRepository.fetchDebugMode(),
		// Strategy.class.getName(), String.valueOf(s.getId()), "strategyGroup
		// is null. @IMP");
		// return;
		// } else if (s.getStrategyGroup().getStatus() != 1) {
		// MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
		// configRepository.fetchDebugMode(),
		// Strategy.class.getName(), String.valueOf(s.getId()),
		// "strategyGroup's status is not available. @IMP");
		// return;
		// }

		List<List<String>> ps = new ArrayList<List<String>>();

		ps.add(toParamList(s.maxImpressionsKey(), 1.0, s.maxImpressionsExpireHour()));
		ps.add(toParamList(s.dayImpressionsKey(), 1.0, 25));

		// 订单计数
		ps.add(toParamList(s.getCampaign().maxImpressionsKey(), 1.0, s.getCampaign().maxImpressionsExpireHour()));
		ps.add(toParamList(s.getCampaign().dayImpressionsKey(), 1.0, 25));

		if (s.getFeeType() == 1) {
			this.incrementCPMBudget(ps, s);
		}

		this.incrTransaction(ps);

		if (s.getFeeType() == 1) {
			if (s.getStrategyGroup() != null && s.getStrategyGroup().getStatus() == 1) {
				if (s.getStrategyGroup().getSpeedMode() == 0) {
					this.decreaseStrategyGroupBalance(s, this.singleCPMBudget(s.getUnitPrice()));
				}
			} else {
				if (s.getSpeedMode() == 0 && (s.getSpeedBase() == 1 || s.getSpeedBase() == 11 || s.getSpeedBase() == 2
						|| s.getSpeedBase() == 12))
					decreaseBalance(s);
			}
		}

	}

	@Override
	public List<Double> findCountValue(Strategy s) {
		List<String> keys = new ArrayList<String>();
		keys.add(s.maxImpressionsKey());
		keys.add(s.dayImpressionsKey());
		keys.add(s.maxClicksKey());
		keys.add(s.dayClicksKey());
		keys.add(s.maxBudgetKey());
		keys.add(s.dayBudgetKey());

		if (s.getCampaign() != null) {
			keys.add(s.getCampaign().maxImpressionsKey());
			keys.add(s.getCampaign().dayImpressionsKey());
			keys.add(s.getCampaign().maxClicksKey());
			keys.add(s.getCampaign().dayClicksKey());
			keys.add(s.getCampaign().maxBudgetKey());
			keys.add(s.getCampaign().dayBudgetKey());
		}

		if (s.getStrategyGroup() != null)
			keys.add(s.getStrategyGroup().dayBudgetKey());

		return this.countOperations.multiGet(keys);
	}

	@Override
	public Double fetchBalance(String key) {
		Number n = countOperations.get(key);
		if (n == null)
			return 0.0;
		else
			return n.doubleValue();
	}

	@Override
	@Deprecated
	public double getStrategyGroupTimeBucketAmount(StrategyGroup sg) {
		int usableHour = 24;

		if (sg.getUsablehour(new Date()) == null) {
			throw new AdServerException(String.valueOf(ResponseStatusCode.UNKNOWN_INNER_ERROR.getValue()),
					"StrategyGroup[id=" + sg.getId() + "] useable hour is null");
		} else {
			usableHour = sg.getUsablehour(new Date());
		}

		return sg.getDayBudget().doubleValue() / usableHour / (60 / this.configRepository.fetchTimeBucket());
	}

	public double getStrategyGroupTimeBucketAmount(StrategyGroupVO sg) {
		int usableHour = 24;

		if (sg.getUsablehour(new Date()) == null) {
			throw new AdServerException(String.valueOf(ResponseStatusCode.UNKNOWN_INNER_ERROR.getValue()),
					"StrategyGroup[id=" + sg.getId() + "] useable hour is null");
		} else {
			usableHour = sg.getUsablehour(new Date());
		}

		return sg.getDayBudget() / usableHour / (60 / this.configRepository.fetchTimeBucket());
	}

	/**
	 * 根据策略的开始时间、结束时间、定向的时间段算出每个时间段的量
	 * 
	 * @param s
	 * @return
	 */
	@Override
	@Deprecated
	public double getTimeBucketAmount(Strategy s) {
		int totalHour = 0;

		DateTime st = new DateTime(s.getStartTime());
		DateTime et = new DateTime(s.getEndTime());

		if (s.getSpeedBase() == 10 || s.getSpeedBase() == 11 || s.getSpeedBase() == 12) {
			DateTime now = DateTime.now();
			st = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), 0, 0);
			et = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), 23, 59);
		}
		while (et.isAfter(st)) {
			if (matchDayTarget(s, st.toDate()))
				totalHour += 1;
			st = st.plusHours(1);
		}
		double maxCount = s.getMaxClicks();
		if (s.getSpeedBase() == 1)
			maxCount = s.getMaxImpressions();
		else if (s.getSpeedBase() == 2) {
			maxCount = s.getBudget() == null ? 0 : s.getBudget().doubleValue();
		} else if (s.getSpeedBase() == 10) {
			maxCount = s.getDayClicks();
		} else if (s.getSpeedBase() == 11) {
			maxCount = s.getDayImpressions();
		} else if (s.getSpeedBase() == 12) {
			maxCount = s.getDayBudget() == null ? 0 : s.getDayBudget().doubleValue();
		}

		return maxCount / totalHour / (60 / this.configRepository.fetchTimeBucket());
	}

	/**
	 * 根据策略的开始时间、结束时间、定向的时间段算出每个时间段的量
	 * 
	 * @param s
	 * @return
	 */
	public double getTimeBucketAmount(StrategyVO s) {
		int totalHour = 0;

		DateTime st = new DateTime(s.getStartTimeSecond());
		DateTime et = new DateTime(s.getEndTimeSecond());

		if (s.getSpeedBase() == 10 || s.getSpeedBase() == 11 || s.getSpeedBase() == 12) {
			DateTime now = DateTime.now();
			st = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), 0, 0);
			et = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), 23, 59);
		}
		while (et.isAfter(st)) {
			if (matchDayTarget(s, st.toDate()))
				totalHour += 1;
			st = st.plusHours(1);
		}
		double maxCount = s.getMaxClicks();
		if (s.getSpeedBase() == 1)
			maxCount = s.getMaxImpressions();
		else if (s.getSpeedBase() == 2) {
			maxCount = s.getBudget();
		} else if (s.getSpeedBase() == 10) {
			maxCount = s.getDayClicks();
		} else if (s.getSpeedBase() == 11) {
			maxCount = s.getDayImpressions();
		} else if (s.getSpeedBase() == 12) {
			maxCount = s.getDayBudget();
		}

		return maxCount / totalHour / (60 / this.configRepository.fetchTimeBucket());
	}

	int getTotalExcept(StrategyVO s, long time, int timeBucket) {
		DateTime now = DateTime.now();
		int exceptHour = 0;
		DateTime updateTime = new DateTime(time);
		if (time == 0) {
			updateTime = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), 0, 0, 0);
		}
		while (now.isAfter(updateTime)) {
			if (!matchDayTarget(s, updateTime.toDate()))
				exceptHour += 1;
			updateTime = updateTime.plusHours(1);
		}
		return exceptHour * (60 / timeBucket);
	}

	@Deprecated
	int getTotalExcept(Strategy s, long time, int timeBucket) {
		DateTime now = DateTime.now();
		int exceptHour = 0;
		DateTime updateTime = new DateTime(time);
		if (time == 0) {
			updateTime = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), 0, 0, 0);
		}
		while (now.isAfter(updateTime)) {
			if (!matchDayTarget(s, updateTime.toDate()))
				exceptHour += 1;
			updateTime = updateTime.plusHours(1);
		}
		return exceptHour * (60 / timeBucket);
	}

	public int getTotalBucket(long s, long e, int timeBucket) {
		DateTime now = new DateTime(e);
		long start = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), 0, 0, 0).getMillis();
		int total = 0;
		if (s == 0) {
			s = start;
		}
		if (s == start)
			total += 1;
		long a = now.getMillis() - start;
		long b = s - start;
		total += a / (timeBucket * 60 * 1000) - b / (timeBucket * 60 * 1000);
		return total;
	}

	// @Override
	public int strategyGroupBucketInterval(long time, StrategyGroupVO sg) {
		int timeBucket = this.configRepository.fetchTimeBucket();
		int total = getTotalBucket(time, System.currentTimeMillis(), timeBucket);
		int dayTarget = 24;
		if (sg.getDayTargetMap(new Date()) == null) {
			throw new AdServerException(String.valueOf(ResponseStatusCode.UNKNOWN_INNER_ERROR.getValue()),
					"StrategyGroup[id=" + sg.getId() + "] dayTarget is null");
			// logger.warn("StrategyGroup[id={}] dayTarget is null",
			// sg.getId());
		} else {
			dayTarget = sg.getDayTargetMap(new Date());
		}

		int except = computeHoursBetween(time == 0 ? 0 : new DateTime(time).getHourOfDay(),
				DateTime.now().getHourOfDay(), dayTarget) * (60 / timeBucket);
		return total - except;
	}

	@Override
	@Deprecated
	public int strategyGroupBucketInterval(long time, StrategyGroup sg) {
		int timeBucket = this.configRepository.fetchTimeBucket();
		int total = getTotalBucket(time, System.currentTimeMillis(), timeBucket);
		int dayTarget = 24;
		if (sg.getDayTargetMap(new Date()) == null) {
			throw new AdServerException(String.valueOf(ResponseStatusCode.UNKNOWN_INNER_ERROR.getValue()),
					"StrategyGroup[id=" + sg.getId() + "] dayTarget is null");
			// logger.warn("StrategyGroup[id={}] dayTarget is null",
			// sg.getId());
		} else {
			dayTarget = sg.getDayTargetMap(new Date());
		}

		int except = computeHoursBetween(time == 0 ? 0 : new DateTime(time).getHourOfDay(),
				DateTime.now().getHourOfDay(), dayTarget) * (60 / timeBucket);
		return total - except;
	}

	public int bucketInterval(long time, StrategyVO s) {
		int timeBucket = this.configRepository.fetchTimeBucket();
		return getTotalBucket(time, System.currentTimeMillis(), timeBucket) - getTotalExcept(s, time, timeBucket);
	}

	@Override
	@Deprecated
	public int bucketInterval(long time, Strategy s) {
		int timeBucket = this.configRepository.fetchTimeBucket();
		return getTotalBucket(time, System.currentTimeMillis(), timeBucket) - getTotalExcept(s, time, timeBucket);
	}

	void decreaseStrategyGroupBalance(StrategyVO s, StrategyGroupVO sg, double singleBudgetOrAmount) {
		// StrategyGroup sg = s.getStrategyGroup();

		long updateTime = this.fetchUpdateTime(StrategyGroup.latestConsumeKey(sg.getId()));
		MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
				sg.getClass().getName(), String.valueOf(sg.getId()),
				"updateTime is " + new DateTime(updateTime).toString("yyyy-MM-dd HH:mm:ss"));
		int bucketIntervalNumber = strategyGroupBucketInterval(updateTime, sg);
		MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
				sg.getClass().getName(), String.valueOf(sg.getId()), "bucketIntervalNumber is " + bucketIntervalNumber);
		// 时间段的间隔差大于0，就需要追量
		if (bucketIntervalNumber > 0) {
			// 判断当前线程是否有追量的许可
			if (addAmountPermissable(StrategyGroup.addAmountCountKey(sg.getId(), updateTime), updateTime)) {
				double timeBucketAmount = getStrategyGroupTimeBucketAmount(sg);
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), sg.getClass().getName(), String.valueOf(sg.getId()),
						"timeBucketAmount is " + timeBucketAmount);
				double total = timeBucketAmount * bucketIntervalNumber;
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), sg.getClass().getName(), String.valueOf(sg.getId()),
						"total is " + total);
				updateTimeTransaction(StrategyGroup.latestConsumeKey(sg.getId()),
						StrategyGroup.timeBucketKey(sg.getId()), total);
			}
		}
		this.countOperations.increment(StrategyGroup.timeBucketKey(sg.getId()), -singleBudgetOrAmount);
	}

	void decreaseStrategyGroupBalance(StrategyGroupVO sg, double singleBudgetOrAmount) {

		long updateTime = this.fetchUpdateTime(StrategyGroup.latestConsumeKey(sg.getId()));
		MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
				sg.getClass().getName(), String.valueOf(sg.getId()),
				"updateTime is " + new DateTime(updateTime).toString("yyyy-MM-dd HH:mm:ss"));
		int bucketIntervalNumber = strategyGroupBucketInterval(updateTime, sg);
		MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
				sg.getClass().getName(), String.valueOf(sg.getId()), "bucketIntervalNumber is " + bucketIntervalNumber);
		// 时间段的间隔差大于0，就需要追量
		if (bucketIntervalNumber > 0) {
			// 判断当前线程是否有追量的许可
			if (addAmountPermissable(StrategyGroup.addAmountCountKey(sg.getId(), updateTime), updateTime)) {
				double timeBucketAmount = getStrategyGroupTimeBucketAmount(sg);
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), sg.getClass().getName(), String.valueOf(sg.getId()),
						"timeBucketAmount is " + timeBucketAmount);
				double total = timeBucketAmount * bucketIntervalNumber;
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), sg.getClass().getName(), String.valueOf(sg.getId()),
						"total is " + total);
				updateTimeTransaction(StrategyGroup.latestConsumeKey(sg.getId()),
						StrategyGroup.timeBucketKey(sg.getId()), total);
			}
		}
		this.countOperations.increment(StrategyGroup.timeBucketKey(sg.getId()), -singleBudgetOrAmount);
	}

	@Deprecated
	void decreaseStrategyGroupBalance(Strategy s, double singleBudgetOrAmount) {
		StrategyGroup sg = s.getStrategyGroup();

		long updateTime = this.fetchUpdateTime(sg.latestConsumeKey());
		MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
				sg.getClass().getName(), String.valueOf(sg.getId()),
				"updateTime is " + new DateTime(updateTime).toString("yyyy-MM-dd HH:mm:ss"));
		int bucketIntervalNumber = strategyGroupBucketInterval(updateTime, sg);
		MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
				sg.getClass().getName(), String.valueOf(sg.getId()), "bucketIntervalNumber is " + bucketIntervalNumber);
		// 时间段的间隔差大于0，就需要追量
		if (bucketIntervalNumber > 0) {
			// 判断当前线程是否有追量的许可
			if (addAmountPermissable(sg.addAmountCountKey(updateTime), updateTime)) {
				double timeBucketAmount = getStrategyGroupTimeBucketAmount(sg);
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), sg.getClass().getName(), String.valueOf(sg.getId()),
						"timeBucketAmount is " + timeBucketAmount);
				double total = timeBucketAmount * bucketIntervalNumber;
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), sg.getClass().getName(), String.valueOf(sg.getId()),
						"total is " + total);
				updateTimeTransaction(sg.latestConsumeKey(), sg.timeBucketKey(), total);
			}
		}
		this.countOperations.increment(sg.timeBucketKey(), -singleBudgetOrAmount);
	}

	void decreaseBalance(StrategyVO s) {
		// 每个时间段的量

		long updateTime = fetchUpdateTime(Strategy.latestConsumeKey(s.getId(), s.getSpeedBase(), s.getFeeType()));
		MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
				s.getClass().getName(), String.valueOf(s.getId()),
				"updateTime is " + new DateTime(updateTime).toString("yyyy-MM-dd HH:mm:ss"));
		// 几个时间间隔
		int bucketIntervalNumber = bucketInterval(updateTime, s);
		MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
				s.getClass().getName(), String.valueOf(s.getId()), "bucketIntervalNumber is " + bucketIntervalNumber);
		// 时间段的间隔差大于0，就需要追量
		if (bucketIntervalNumber > 0) {
			// 判断当前线程是否有追量的许可
			if (addAmountPermissable(Strategy.addAmountCountKey(s.getId(), updateTime), updateTime)) {
				double timeBucketAmount = getTimeBucketAmount(s);
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), s.getClass().getName(), String.valueOf(s.getId()),
						"timeBucketAmount is " + timeBucketAmount);
				double total = timeBucketAmount * bucketIntervalNumber;
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), s.getClass().getName(), String.valueOf(s.getId()),
						"total is " + total);
				// balanceLogger.info("-----------------追量的数字：{}", total);
				updateTimeTransaction(Strategy.latestConsumeKey(s.getId(), s.getSpeedBase(), s.getFeeType()),
						Strategy.timeBucketKey(s.getId(), s.getSpeedBase(), s.getFeeType()), total);
			}
		}

		double singleBudgetOrAmount = 1;
		if (s.getSpeedBase() == 2 || s.getSpeedBase() == 12) {
			if (s.getFeeType() == 0)
				singleBudgetOrAmount = s.getUnitPrice();
			else if (s.getFeeType() == 1) {
				singleBudgetOrAmount = this.singleCPMBudget(new BigDecimal(s.getUnitPrice()));
			}
		}

		this.countOperations.increment(Strategy.timeBucketKey(s.getId(), s.getSpeedBase(), s.getFeeType()),
				-singleBudgetOrAmount);
	}

	/**
	 * 根据speedBase减少每个时间段的余量 1.计算时间间隔数，计算需要追加的量 2.根据speedBase减少
	 * 
	 * @param s
	 */
	@Deprecated
	void decreaseBalance(Strategy s) {
		// 每个时间段的量

		long updateTime = fetchUpdateTime(s.latestConsumeKey());
		MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
				s.getClass().getName(), String.valueOf(s.getId()),
				"updateTime is " + new DateTime(updateTime).toString("yyyy-MM-dd HH:mm:ss"));
		// 几个时间间隔
		int bucketIntervalNumber = bucketInterval(updateTime, s);
		MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
				s.getClass().getName(), String.valueOf(s.getId()), "bucketIntervalNumber is " + bucketIntervalNumber);
		// 时间段的间隔差大于0，就需要追量
		if (bucketIntervalNumber > 0) {
			// 判断当前线程是否有追量的许可
			if (addAmountPermissable(s.addAmountCountKey(updateTime), updateTime)) {
				double timeBucketAmount = getTimeBucketAmount(s);
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), s.getClass().getName(), String.valueOf(s.getId()),
						"timeBucketAmount is " + timeBucketAmount);
				double total = timeBucketAmount * bucketIntervalNumber;
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), s.getClass().getName(), String.valueOf(s.getId()),
						"total is " + total);
				// balanceLogger.info("-----------------追量的数字：{}", total);
				updateTimeTransaction(s.latestConsumeKey(), s.timeBucketKey(), total);
			}
		}

		double singleBudgetOrAmount = 1;
		if (s.getSpeedBase() == 2 || s.getSpeedBase() == 12) {
			if (s.getFeeType() == 0)
				singleBudgetOrAmount = s.getUnitPrice().doubleValue();
			else if (s.getFeeType() == 1) {
				singleBudgetOrAmount = this.singleCPMBudget(s.getUnitPrice());
			}
		}

		this.countOperations.increment(s.timeBucketKey(), -singleBudgetOrAmount);
	}

	/**
	 * 检查是否当前可以有追量的许可： 1.以服务器ID加最新更新时间作为key计数，累加值为1。 2.如果计数值小于等于1，返回true。
	 * 3.true代表有追量的许可。
	 * 
	 * @return
	 */
	boolean addAmountPermissable(final String key, final long updateTime) {
		Number n = this.countOperations.increment(key, 1);
		this.countRedisTemplate.expire(key, 25, TimeUnit.HOURS);

		int i = n != null ? n.intValue() : 0;
		return i <= 1;
	}

	// redis的事务处理，保证两条命令是顺序执行
	void updateTimeTransaction(final String latestConsumeKey, final String timeBucketKey, final double total) {

		countRedisTemplate.execute(new SessionCallback<List<Object>>() {
			@SuppressWarnings({ "rawtypes", "unchecked" })
			public List<Object> execute(RedisOperations operations) throws DataAccessException {
				operations.multi();
				operations.opsForValue().set(latestConsumeKey, String.valueOf(DateTime.now().getMillis()));
				operations.opsForValue().increment(timeBucketKey, total);
				return operations.exec();
			}
		});

	}

	/**
	 * 时间定向。匹配单位为小时。匹配周期为一周。 参数 date为null 则为当前时间。
	 * 
	 * @param strategy
	 * @param date
	 * @return
	 */
	@Deprecated
	boolean matchDayTarget(Strategy strategy, Date date) {
		DayTargert dayTargert = strategy.getDayTargert();
		if (strategy == null || strategy.getDayTargert() == null) {
			throw new IllegalArgumentException("org strategy illegal ");
		}
		Calendar calendar = Calendar.getInstance();
		if (date != null) {
			calendar.setTime(date);
		}
		int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
		int hour = calendar.get(Calendar.HOUR_OF_DAY);
		boolean validated = isTargetingTimeInternalRange(dayOfWeek, hour, dayTargert.toArray());
		return validated;
	}

	/**
	 * 时间定向。匹配单位为小时。匹配周期为一周。 参数 date为null 则为当前时间。
	 * 
	 * @param strategy
	 * @param date
	 * @return
	 */
	boolean matchDayTarget(StrategyVO strategy, Date date) {
		DayTargert dayTargert = strategy.getDayTargert();
		if (strategy == null || strategy.getDayTargert() == null) {
			throw new IllegalArgumentException("org strategy illegal ");
		}
		Calendar calendar = Calendar.getInstance();
		if (date != null) {
			calendar.setTime(date);
		}
		int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
		int hour = calendar.get(Calendar.HOUR_OF_DAY);
		boolean validated = isTargetingTimeInternalRange(dayOfWeek, hour, dayTargert.toArray());
		return validated;
	}

	/**
	 * 配合方法 matchDayTarget(Strategy strategy, Date date)
	 * 
	 * @param dayOfWeek
	 * @param hour
	 * @param ranges
	 * @return
	 */
	boolean isTargetingTimeInternalRange(int dayOfWeek, int hour, Integer[] ranges) {
		return (ranges[dayOfWeek] & masks[hour]) != 0 ? true : false;
	}

	@Override
	public long fetchUpdateTime(String key) {
		String v = this.countRedisTemplate.opsForValue().get(key);
		if (StringUtils.isEmpty(v))
			return 0;
		else
			return Long.valueOf(v);
	}

	@Override
	public Strategy findById(long id) {
		if (id == 0)
			return null;
		return this.strategyOperations.get(Strategy.cacheKey(id));
	}

	// @Autowired
	// private AssembledCachedDataContainer container;

	// private ConcurrentHashMap<Integer, StrategyGoupMap> mapStrategyGroupAll;
	// TODO 此方法移动到job中
	/**
	 * 第一次加载所有。以后只加载变化的数据。
	 */
	@Override
	public void warmUpStrategy() {
		// this.mapStrategyGroupAll = container.getMapStrategyGroupAll();
		String warmUpTime = stringValueOperations.get(Constants.WARM_UP_TIME);
		List<Strategy> strategys = null;
		strategys = strategyDao.findAll2();
		for (Strategy s : strategys) {
			try {
				if (s.getStatus() == 1) {
					s = attachCampaign(s);
					addDayTarget(s);
					attachGroup(s);
					strategyOperations.set(s.toCacheKey(), s);
				} else if (s.getStatus() != 1) {
					cacheRedisTemplate.delete(s.toCacheKey());
				}
			} catch (Exception e) {
				if (s != null) {
					logger.error("错误：在获取strategy_id =" + s.getId() + "数据时发生错误 \n 错误信息：", e);
				}
			}
		}
	}

	// TODO 此方法移动到job中
	/**
	 * 附上策略组
	 * 
	 * @param strategy
	 */
	private void attachGroup(Strategy strategy) {
		if (strategy == null) {
			return;
		}

		// StrategyGroup strategyGroup = strategy.getStrategyGroup();
		// if (strategyGroup == null) {
		// return;
		// }
		int strategyGroupId = strategy.getStrategyGroupId();
		// if (strategyGroupId != 0) {
		// StrategyGoupMap strategyGoupMap =
		// mapStrategyGroupAll.get(strategyGroupId);
		// if (strategyGoupMap != null) {
		// strategy.setStrategyGroup(strategyGoupMap.getGroup());
		// }
		// }

	}

	/**
	 * 附上Campaign对象
	 * 
	 * @param strategy
	 * @return
	 */
	private Strategy attachCampaign(Strategy strategy) {

		if (strategy == null) {
			return null;
		}

		Campaign campaign = strategy.getCampaign();
		if (campaign != null) {
			Campaign _campaign = campaignDao.selectById(campaign.getId(), STATUS_VALID);
			strategy.setCampaign(_campaign);
		}
		return strategy;
	}

	@Autowired
	private DayPartingDao dayPartingDao;

	/**
	 * 添加时间定向。 FIXME 在adSpaceRepoositoryImpl中有重复
	 * 
	 * @param strategy
	 */
	private void addDayTarget(Strategy strategy) {
		if (strategy == null) {
			return;
		}
		/**
		 * 时间定向
		 */
		List<DayTargert> dayTargerts = dayPartingDao.selectAllByStrategyId(strategy.getId(), 1);
		if (dayTargerts.size() > 0) {
			DayTargert dayTargert = dayTargerts.get(0);

			if (dayTargert.getMonday() == 0 && dayTargert.getTuesday() == 0 && dayTargert.getWednesday() == 0
					&& dayTargert.getThursday() == 0 && dayTargert.getFriday() == 0 && dayTargert.getSaturday() == 0
					&& dayTargert.getSunday() == 0) {
				int allHours = 16777215;
				dayTargert.setMonday(allHours);
				dayTargert.setTuesday(allHours);
				dayTargert.setWednesday(allHours);
				dayTargert.setThursday(allHours);
				dayTargert.setFriday(allHours);
				dayTargert.setSaturday(allHours);
				dayTargert.setSunday(allHours);
				strategy.setDayTargert(dayTargert);
			}
			strategy.setDayTargert(dayTargert);
		} else {
			// 如果没有则默认一周这个时间段全投放
			DayTargert dayTargert = new DayTargert();
			int allHours = 16777215;
			dayTargert.setMonday(allHours);
			dayTargert.setTuesday(allHours);
			dayTargert.setWednesday(allHours);
			dayTargert.setThursday(allHours);
			dayTargert.setFriday(allHours);
			dayTargert.setSaturday(allHours);
			dayTargert.setSunday(allHours);
			strategy.setDayTargert(dayTargert);
		}
	}

	@Override
	public List<Strategy> findAvailable() {
		return this.strategyDao.findAvailable();
	}

	@Override
	public List<AmountStore> findAmounts(Strategy s) {
		List<String> keys = new ArrayList<String>();
		keys.add(s.maxClicksKey());
		keys.add(s.dayClicksKey());
		keys.add(s.maxImpressionsKey());
		keys.add(s.dayImpressionsKey());
		keys.add(s.maxBudgetKey());
		keys.add(s.dayBudgetKey());

		if (s.getSpeedMode() == 0) {
			for (String hostName : this.configRepository.fetchAllHostName()) {
				// to-do
				// keys.add(s.timeBucketKey(hostName));
			}
		}
		List<Double> amounts = this.countOperations.multiGet(keys);

		// Map<String, Number> result = new HashMap<String, Number>();
		List<AmountStore> result = new ArrayList<AmountStore>();
		if (CollectionUtils.isEmpty(amounts))
			return result;

		for (int i = 0; i < keys.size(); i++) {
			Number n = amounts.get(i);
			if (n != null) {
				AmountStore as = new AmountStore();
				as.setObjectType(1);
				as.setObjectId(s.getId());
				as.setCacheKey(keys.get(i));
				as.setAmount(n.doubleValue());
				as.setAmountType(i < 6 ? i + 1 : 7);
				result.add(as);
			}
		}

		return result;
	}

	/**
	 * 计算小时数。 start >=0 end <=23
	 * 
	 * @param startHour
	 * @param endHour
	 * @param src
	 * @return
	 */
	public Integer computeHoursBetween(int startHour, long endHour, int src) {
		int exceptHours = 0;
		for (long i = startHour; i <= endHour; i++) {
			if ((src & (1 << i)) == 0) {
				exceptHours++;
			}
		}
		return exceptHours;
	}

	@Override
	public Double fetchAmount(String key) {
		Number n = this.countOperations.get(key);
		if (n != null)
			return n.doubleValue();
		return 0d;
	}

	public boolean whetherHaveBalance(List<String> keys, List<Double> targes) {
		return this.countRedisTemplate.execute(balanceScript, keys, targes.toArray(new Double[targes.size()]));
	}

	@Override
	public void unbond() {
		RedisConnectionUtils.unbindConnection(this.cacheRedisTemplate.getConnectionFactory());
		RedisConnectionUtils.unbindConnection(this.countRedisTemplate.getConnectionFactory());
		RedisConnectionUtils.unbindConnection(this.guidRedisTemplate.getConnectionFactory());
		RedisConnectionUtils.unbindConnection(this.guidRedisTemplateV2.getConnectionFactory());
	}

	@Override
	public void countClick(GuidValueObject guidVO) {
		StrategyVO s = guidVO.getStrategyVO();
		CampaignVO c = guidVO.getCampaignVO();
		StrategyGroupVO sg = guidVO.getStrategyGroupVO();
		if (s == null) {
			MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
					Strategy.class.getName(), "0", "is null. @CLICK");
			return;
		} else if (c == null) {
			MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
					Strategy.class.getName(), String.valueOf(guidVO.getSid()), "campaign is null. @CLICK");
			return;
		}
		// else if (sg == null) {
		// MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
		// configRepository.fetchDebugMode(),
		// Strategy.class.getName(),
		// String.valueOf(guidVO.getStrategyGroupVO().getId()),
		// "strategyGroup is null. @CLICK");
		// return;
		// } else if (sg.getStatus() != 1) {
		// MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
		// configRepository.fetchDebugMode(),
		// Strategy.class.getName(),
		// String.valueOf(guidVO.getStrategyGroupVO().getId()),
		// "strategyGroup's status is not available. @CLICK");
		// return;
		// }

		List<List<String>> ps = new ArrayList<List<String>>();

		// // 订单计数

		// ps.add(toParamList(s.getCampaign().maxClicksKey(), 1.0,
		// s.getCampaign().maxClicksExpireHour()));
		ps.add(toParamList(Campaign.maxClicksKey(guidVO.getCampaignId()), 1.0,
				Campaign.maxClicksExpireHour(c.getEndTimeSecond())));

		// ps.add(toParamList(s.getCampaign().dayClicksKey(), 1.0, 25));
		ps.add(toParamList(Campaign.dayClicksKey(c.getId()), 1.0, 25));
		//
		// ps.add(toParamList(s.maxClicksKey(), 1.0, s.maxClicksExpireHour()));
		ps.add(toParamList(Strategy.maxClicksKey(s.getId()), 1.0, Strategy.maxClicksExpireHour(s.getEndTimeSecond())));
		// //ps.add(toParamList(s.dayClicksKey(), 1.0, 25));
		ps.add(toParamList(Strategy.dayClicksKey(s.getId()), 1.0, 25));
		//
		if (s.getFeeType() == 0)
			this.incrementCPCBudget(ps, s, c, sg);

		// long ss = System.currentTimeMillis();
		this.incrTransaction(ps);
		// logger.info("----- {}", System.currentTimeMillis() - ss);
		//
		if (s.getFeeType() == 0) {
			if (sg != null && sg.getStatus() == 1) {
				if (sg.getSpeedMode() == 0) {
					decreaseStrategyGroupBalance(sg, s.getUnitPrice());
				}
			} else {
				if (s.getSpeedMode() == 0 && (s.getSpeedBase() == 0 || s.getSpeedBase() == 10 || s.getSpeedBase() == 2
						|| s.getSpeedBase() == 12))
					decreaseBalance(s);
			}
		}
	}

	@Override
	public void countImpressions(GuidValueObject guidVO) {
		if (guidVO == null)
			return;
		StrategyVO s = guidVO.getStrategyVO();
		CampaignVO c = guidVO.getCampaignVO();
		StrategyGroupVO sg = guidVO.getStrategyGroupVO();
		if (s == null) {
			MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
					Strategy.class.getName(), "0", "is null. @IMP");
			return;
		} else if (c == null) {
			MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(), configRepository.fetchDebugMode(),
					Strategy.class.getName(), String.valueOf(s.getId()), "campaign is null. @IMP");
			return;
		}
		// else if (sg == null) {
		// MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
		// configRepository.fetchDebugMode(),
		// Strategy.class.getName(), String.valueOf(s.getId()), "strategyGroup
		// is null. @IMP");
		// return;
		// } else if (sg.getStatus() != 1) {
		// MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
		// configRepository.fetchDebugMode(),
		// Strategy.class.getName(), String.valueOf(s.getId()),
		// "strategyGroup's status is not available. @IMP");
		// return;
		// }

		List<List<String>> ps = new ArrayList<List<String>>();

		ps.add(toParamList(Strategy.maxImpressionsKey(s.getId()), 1.0,
				Strategy.maxImpressionsExpireHour(s.getEndTimeSecond())));
		ps.add(toParamList(Strategy.dayImpressionsKey(s.getId()), 1.0, 25));

		// 订单计数
		ps.add(toParamList(Campaign.maxImpressionsKey(c.getId()), 1.0,
				Campaign.maxImpressionsExpireHour(c.getEndTimeSecond())));
		ps.add(toParamList(Campaign.dayImpressionsKey(c.getId()), 1.0, 25));

		if (s.getFeeType() == 1) {
			this.incrementCPMBudget(ps, s, c, sg);
		}

		this.incrTransaction(ps);

		if (s.getFeeType() == 1) {
			if (sg != null && sg.getStatus() == 1) {
				if (sg.getSpeedMode() == 0) {
					this.decreaseStrategyGroupBalance(sg, this.singleCPMBudget(s.getUnitPrice()));
				}
			} else {
				if (s.getSpeedMode() == 0 && (s.getSpeedBase() == 1 || s.getSpeedBase() == 11 || s.getSpeedBase() == 2
						|| s.getSpeedBase() == 12))
					decreaseBalance(s);
			}
		}

	}

}
